#define _GNU_SOURCE
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include "HttpRequest.h"
#include "Log.h"
#include "TcpConnection.h"

#define HEADERSIZE 12

struct HttpRequest *httpRequestInit()
{
    struct HttpRequest *httpRequest = (struct HttpRequest *)malloc(sizeof(struct HttpRequest));
    httpRequestReset(httpRequest);
    httpRequest->reqHeaders = (struct RequestHeader *)malloc(sizeof(struct RequestHeader) * HEADERSIZE);
    return httpRequest;
}

void httpRequestReset(struct HttpRequest *req)
{
    req->curState = ParseReqLine;
    req->method = NULL;
    req->reqHeadersNum = 0;
    req->url = NULL;
    req->version = NULL;
}

void httpRequestResetEx(struct HttpRequest *req)
{
    free(req->url);
    free(req->method);
    free(req->version);
    if (req->reqHeaders != NULL)
    {
        for (int32_t i = 0; i < req->reqHeadersNum; i++)
        {
            free(req->reqHeaders[i].key);
            free(req->reqHeaders[i].value);
        }
    }
    httpRequestReset(req);
}

void httpRequestDestroy(struct HttpRequest *req)
{
    if (req != NULL)
    {
        httpRequestResetEx(req);
        free(req);
    }
}

enum HttpRequestState HttpRequestState(struct HttpRequest *request)
{
    return request->curState;
}

void httpRequestAddHeader(struct HttpRequest *request, char *key, char *value)
{
    request->reqHeaders[request->reqHeadersNum].key = key;
    request->reqHeaders[request->reqHeadersNum].value = value;
    request->reqHeadersNum++;
}

char *httpRequestGetHeader(struct HttpRequest *request, const char *key)
{
    if (request != NULL)
    {
        for (int32_t i = 0; i < request->reqHeadersNum; ++i)
        {
            if (strncasecmp(request->reqHeaders[i].key, key, strlen(key) == 0))
            {
                return request->reqHeaders[i].value;
            }
        }
    }
    return NULL;
}

const char *splitRequestLine(const char *start, const char *end, const char *sub, char **ptr)
{
    const char *space = end;
    if (sub != NULL)
    {
        space = memmem(start, end - start, sub, strlen(sub));
        assert(space != NULL);
    }
    int32_t length = space - start;
    char *tmp = (char *)malloc(length + 1);

    strncpy(tmp, start, length);
    tmp[length] = '\0';
    *ptr = tmp;
    return space + 1;
}

bool parseHttpRequestLine(struct HttpRequest *reqeuset, struct Buffer *readBuff)
{
    // 读出请求行
    char *end = bufferFindCRLF(readBuff);
    // 保存字符床起始地址
    const char *start = readBuff->data + readBuff->readPos;
    // 保存字符床结束地址
    int32_t lineSize = end - start;

    if (lineSize)
    {
        start = splitRequestLine(start, end, " ", &reqeuset->method);
        start = splitRequestLine(start, end, " ", &reqeuset->url);
        start = splitRequestLine(start, end, NULL, &reqeuset->version);
#if 0
        // get /xxx/xx.txt http/1.1
        // 请求方式
        char *space = (char *)memmem(start, lineSize, " ", 1);
        assert(space != NULL);
        int32_t methodSize = space - start;
        reqeuset->method = (char *)malloc(methodSize + 1);
        strncpy(reqeuset->method, start, methodSize);
        reqeuset->method[methodSize] = '\0';

        // 请求的静态资源
        start = space + 1;
        space = (char *)memmem(start, end - start, " ", 1);
        assert(space != NULL);
        int32_t urlSize = space - start;
        reqeuset->url = (char *)malloc(urlSize + 1);
        strncpy(reqeuset->url, start, urlSize);
        reqeuset->url[urlSize] = '\0';

        // http版本
        start = space + 1;

        int32_t versionSize = end - start;
        reqeuset->version = (char *)malloc(versionSize + 1);
        strncpy(reqeuset->version, start, versionSize);
        reqeuset->version[versionSize] = '\0';
#endif
        // 为解析请求头做准备
        readBuff->readPos += lineSize;
        readBuff->readPos += 2;
        // 修改状态
        reqeuset->curState = ParseReqHeaders;
        return true;
    }
    return false;
}

// 该函数处理请求头的一行
bool parseHttpRequestHeader(struct HttpRequest *reqeuset, struct Buffer *readBuff)
{
    char *end = bufferFindCRLF(readBuff);
    if (end != NULL)
    {
        char *start = readBuff->data + readBuff->readPos;
        int32_t lineSize = end - start;
        // 基于：搜索字符串
        char *middle = memmem(start, lineSize, ": ", 2);
        if (middle != NULL)
        {
            char *key = malloc(middle - start + 1);
            strncpy(key, start, middle - start);
            key[middle - start] = '\0';

            char *value = malloc(end - middle - 2 + 1);
            strncpy(value, middle + 2, end - middle - 2);
            value[end - middle - 2] = '\0';

            httpRequestAddHeader(reqeuset, key, value);
            // 移动读数据的位置
            readBuff->readPos += lineSize;
            readBuff->readPos += 2;
        }
        else
        {
            // 请求头被解析完了,跳过空行
            readBuff->readPos += 2;
            // 修改解析状态
            // 忽略post请求 按照get请求处理
            reqeuset->curState = ParseReqDone;
        }
        return true;
    }
    return false;
}

bool parseHttpRequest(struct HttpRequest *reqeuset, struct Buffer *readBuff, struct HttpResponse *response, struct Buffer *sendBuff, int32_t socket)
{   
    printf("开始解析消息  cfd = %d\n", socket);
    bool flag = true;
    while (reqeuset->curState != ParseReqDone)
    {
        switch (reqeuset->curState)
        {
        case ParseReqLine:
            flag = parseHttpRequestLine(reqeuset, readBuff);
            break;
        case ParseReqHeaders:
            flag = parseHttpRequestHeader(reqeuset, readBuff);
            break;
        case ParseReqBody:
            break;
        default:
            break;
        }
        if (!flag)
        {
            return flag;
        }
        // 判断是否解析完毕了， 如果完毕了，需要准备回复的数据
        if (reqeuset->curState == ParseReqDone)
        {
            // 1.根据解析出的原始数据，对客户端的请求做出处理
            processHttpRequest(reqeuset, response);
            printf("解析完毕消息，开始发送 cfd = %d\n", socket);
            // 2.组织响应数据并发送客户端
            httpResponsePrepareMsg(response, sendBuff, socket);
        }
    }
    reqeuset->curState = ParseReqLine; // 状态还原，保证还能继续处理第二条以及以后的请求
    return flag;
}

// 将字符转换为整形数
int hexToDec(char c)
{
    if (c >= '0' && c <= '9')
        return c - '0';
    if (c >= 'a' && c <= 'f')
        return c - 'a' + 10;
    if (c >= 'A' && c <= 'F')
        return c - 'A' + 10;

    return 0;
}

// 解码
// to 存储解码之后的数据, 传出参数, from被解码的数据, 传入参数
void decodeMsg(char *to, char *from)
{
    for (; *from != '\0'; ++to, ++from)
    {
        // isxdigit -> 判断字符是不是16进制格式, 取值在 0-f
        // Linux%E5%86%85%E6%A0%B8.jpg
        if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2]))
        {
            // 将16进制的数 -> 十进制 将这个数值赋值给了字符 int -> char
            // B2 == 178
            // 将3个字符, 变成了一个字符, 这个字符就是原始数据
            *to = hexToDec(from[1]) * 16 + hexToDec(from[2]);

            // 跳过 from[1] 和 from[2] 因此在当前循环中已经处理过了
            from += 2;
        }
        else
        {
            // 字符拷贝, 赋值
            *to = *from;
        }
    }
    *to = '\0';
}

// 处理基于get的http请求
bool processHttpRequest(struct HttpRequest *reqeuset, struct HttpResponse *response)
{
    if (strcasecmp(reqeuset->method, "get") != 0)
    {
        return -1;
    }
    decodeMsg(reqeuset->url, reqeuset->url);
    // 处理客户端请求的静态资源（目录或者文件）
    char *file = NULL;
    if (strcmp(reqeuset->url, "/") == 0)
    {
        file = "./";
    }
    else
    {
        file = reqeuset->url + 1;
    }
    struct stat st;
    int ret = stat(file, &st);
    if (ret == -1)
    {
        printf("file not exist\n");
        // 文件不存在 -- 回复404
        strcpy(response->fileName, "404.html");
        response->statusCode = NotFound;
        strcpy(response->statusMsg, "Not Found");
        // 响应头
        const char *fileType = getFileType(".html");
        httpResponseAddHeader(response, "Content-type", fileType);
        response->sendDataFunc = sendFile;
        return true;
    }
    strcpy(response->fileName, file);
    response->statusCode = OK;
    strcpy(response->statusMsg, "OK");
    // 判断文件类型
    if (S_ISDIR(st.st_mode))
    {
        // 响应头
        const char *fileType = getFileType(".html");
        httpResponseAddHeader(response, "Content-type", fileType);
        response->sendDataFunc = sendDir;
    }
    else
    {
        // 把文件的内容发送给客户端
        // 响应头
        char tmp[12] = {0};
        sprintf(tmp, "%ld", st.st_size);
        httpResponseAddHeader(response, "Content-length", tmp);
        const char *fileType = getFileType(file);
        httpResponseAddHeader(response, "Content-type", fileType);
        response->sendDataFunc = sendFile;
    }
    return true;
}

const char *getFileType(const char *fileName)
{
    const char *dot = strrchr(fileName, '.');

    if (dot == NULL)
        return "text/plain; charset=utf-8"; // 纯文本
    if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0)
        return "text/html; charset=utf-8";
    if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)
        return "image/jpeg";
    if (strcmp(dot, ".gif") == 0)
        return "image/gif";
    if (strcmp(dot, ".png") == 0)
        return "image/png";
    if (strcmp(dot, ".css") == 0)
        return "text/css";
    if (strcmp(dot, ".au") == 0)
        return "audio/basic";
    if (strcmp(dot, ".wav") == 0)
        return "audio/wav";
    if (strcmp(dot, ".avi") == 0)
        return "video/x-msvideo";
    if (strcmp(dot, ".mov") == 0 || strcmp(dot, ".qt") == 0)
        return "video/quicktime";
    if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0)
        return "video/mpeg";
    if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0)
        return "model/vrml";
    if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0)
        return "audio/midi";
    if (strcmp(dot, ".mp3") == 0)
        return "audio/mpeg";
    if (strcmp(dot, ".ogg") == 0)
        return "application/ogg";
    if (strcmp(dot, ".pac") == 0)
        return "application/x-ns-proxy-autoconfig";

    return "text/plain; charset=utf-8";
}

void sendFile(const char *fileName, struct Buffer *sendBuff, int32_t cfd)
{   
    int32_t fd = open(fileName, O_RDONLY);
    if (fd < 0)
    {
        perror("open error");
    }
    assert(fd > 0);
#if 1
    char buff[1024] = {0};
    int32_t len = 0;
    while ((len = read(fd, buff, sizeof(buff))) > 0)
    {
        bufferAppendData(sendBuff, buff, len);
        memset(buff, 0, sizeof(buff));
#ifndef MSG_SEND_AUTO
        bufferSendData(sendBuff, cfd);
#endif
    }
    if (len == -1)
    {
        close(fd);
        perror("read error");
    }
    printf("发送cfd = %d fileName = %s 到sendbuff\n", cfd, fileName);
#else
    int64_t size = lseek(fd, 0, SEEK_END);
    lseek(fd, 0, SEEK_SET); // 重要！让文件指针回到开头
    off_t offset = 0;
    int64_t remaining = size;

    while (remaining > 0)
    {
        ssize_t sent = sendfile(cfd, fd, &offset, remaining);
        if (sent == -1)
        {
            if (errno == EAGAIN || errno == EWOULDBLOCK)
            {
                // 等待 socket 可写再继续发送
                printf("无数据\n");
                continue;
            }
            perror("sendfile error");
            break;
        }
        remaining -= sent;
    }

#endif
    close(fd);
    return;
}

void sendDir(const char *dirName, struct Buffer *sendBuff, int32_t cfd)
{
    char buff[4096] = {0};
    snprintf(buff, sizeof(buff), "<html><head><title>%s</title></head><body><table>", dirName);
    struct dirent **nameList = NULL;
    int num = scandir(dirName, &nameList, NULL, alphasort);
    for (int i = 0; i < num; ++i)
    {
        // 取出文件名
        char *name = nameList[i]->d_name;
        struct stat st;
        char subPath[1024] = {0};
        snprintf(subPath, sizeof(subPath), "%s/%s", dirName, name);
        stat(subPath, &st);
        if (S_ISDIR(st.st_mode))
        {
            // a标签 <a href="">name</a>
            snprintf(buff + strlen(buff), sizeof(buff), "<tr><td><a href=\"%s/\">%s</a></td><td>%ld</td></tr>",
                     name, name, st.st_size);
        }
        else
        {
            snprintf(buff + strlen(buff), sizeof(buff), "<tr><td><a href=\"%s\">%s</a></td><td>%ld</td></tr>",
                     name, name, st.st_size);
        }
        bufferAppendString(sendBuff, buff);
        memset(buff, 0, sizeof(buff));
        free(nameList[i]);
    }
    snprintf(buff, strlen(buff), "</table></body></html>");
    bufferAppendString(sendBuff, buff);
    printf("直接发送目录   cfd = %d dirName = %s\n", cfd, dirName);
    bufferSendData(sendBuff, cfd);
    free(nameList);
    return;
}